﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Blade.IO {
	public abstract class CursorStream : Stream {
		public virtual CursorStream CreateCursor(long offset, long count, bool writable) {
			if (count < 0)
				throw new ArgumentOutOfRangeException("count");
			if (offset < 0 || offset + count > Length)
				throw new ArgumentOutOfRangeException("offset");
			return new ChildCursorStream(this, offset, count, writable);
		}

		public virtual CursorStream Duplicate(bool writable) { return CreateCursor(0, this.Length, writable); }

		public sealed override int Read(byte[] buffer, int offset, int count) {
			var read = Read(this.Position, buffer, offset, count);
			Position += read;
			return read;
		}
		public sealed override void Write(byte[] buffer, int offset, int count) {
			Write(this.Position, buffer, offset, count);
		}

		public int Read(long position, byte[] buffer, int offset, int count) {
			if (count < 0)
				throw new ArgumentOutOfRangeException("count");
			if (position < 0 || position + count > Length)
				throw new ArgumentOutOfRangeException("position");
			return ReadCore(position, buffer, offset, count);
		}
		public void Write(long position, byte[] buffer, int offset, int count) {
			if (count < 0)
				throw new ArgumentOutOfRangeException("count");
			if (position < 0 || position + count > Length)
				throw new ArgumentOutOfRangeException("position");
			WriteCore(position, buffer, offset, count);
		}

		protected abstract int ReadCore(long position, byte[] buffer, int offset, int count);
		protected abstract void WriteCore(long position, byte[] buffer, int offset, int count);
		public sealed override void SetLength(long value) {
			throw new NotSupportedException();
		}

		public override long Seek(long offset, SeekOrigin origin) {
			switch (origin) {
				case SeekOrigin.Begin: break;
				case SeekOrigin.Current: offset += this.Position; break;
				case SeekOrigin.End: offset += this.Length; break;
				default: throw new NotSupportedException();
			}
			var old = Position;
			this.Position = offset;
			return old;
		}

		sealed class ChildCursorStream : CursorStream {
			CursorStream parent;
			long offset;
			long count;
			bool writable;
			long position;

			public ChildCursorStream(CursorStream parent, long offset, long count, bool writable) {
				this.parent = parent;
				this.offset = offset;
				this.count = count;
				this.writable = writable;
				this.position = 0;
			}

			public override bool CanRead { get { return parent.CanRead; } }
			public override bool CanSeek { get { return parent.CanSeek; } }
			public override bool CanWrite { get { return writable && parent.CanWrite; } }
			public override void Flush() { parent.Flush(); }
			public override long Length { get { return count; } }

			public override long Position {
				get { return position; }
				set {
					if (value < 0 || value > count)
						throw new ArgumentOutOfRangeException("value", "value is outside the bounds of the cursor");
					position = value;
				}
			}

			protected override int ReadCore(long position, byte[] buffer, int offset, int count) {
				return parent.Read(
					this.offset + position,
					buffer,
					offset,
					count
				);
			}

			protected override void WriteCore(long position, byte[] buffer, int offset, int count) {
				parent.Write(
					this.offset + position,
					buffer,
					offset,
					count
				);
			}
		}
	}
}
